/*************************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2004.
-------------------------------------------------------------------------
$Id$
$DateTime$

-------------------------------------------------------------------------
History:
- 28:7:2004   11:41 : Created by Marco Koegler
- 30:7:2004   11:02 : Taken-over by Marcio Martins
- xx:x:2006		xx:xx : Taken-over by Jan Mueller
- 30:08:2006  21:12 : CraigT Made dedicated server .exe

*************************************************************************/
#include "StdAfx.h"
#include <CryLibrary.h>
#include <IGameStartup.h>
#include <IConsole.h>
#include "platform_impl.h"

#define WIN32_LEAN_AND_MEAN
#include "StdAfx.h"
#include <windows.h>
#include <ShellAPI.h>

// We need shell api for Current Root Extrection.
#include "shlwapi.h"
#pragma comment(lib, "shlwapi.lib")
#ifdef _DEBUG
#define DEFAULT_GAMEDLL_FILENAME   "CryGame_d.dll"
#else
#define DEFAULT_GAMEDLL_FILENAME   "CryGame.dll"
#endif
#define CONFIG_KEY_FOR_GAMEDLL     "sys_dll_game"
#define CONFIG_KEY_IGNORE_TOO_MANY_APPS "ignoreTooManyAppsMessage"
#ifdef _DEBUG
#define ENGINE_CFG_FILE  "system_d.cfg"
#else
#define ENGINE_CFG_FILE  "system.cfg"
#endif

static unsigned key[4] = {1339822019,3471820962,4179589276,4119647811};
static unsigned text[4] = {4114048726,1217549643,1454516917,859556405};
static unsigned hash[4] = {324609294,3710280652,1292597317,513556273};

// src and trg can be the same pointer (in place encryption)
// len must be in bytes and must be multiple of 8 byts (64bits).
// key is 128bit:  int key[4] = {n1,n2,n3,n4};
// void encipher(unsigned int *const v,unsigned int *const w,const unsigned int *const k )
#define TEA_ENCODE( src,trg,len,key ) {\
	register unsigned int *v = (src), *w = (trg), *k = (key), nlen = (len) >> 3; \
	register unsigned int delta=0x9E3779B9,a=k[0],b=k[1],c=k[2],d=k[3]; \
	while (nlen--) {\
	register unsigned int y=v[0],z=v[1],n=32,sum=0; \
	while(n-->0) { sum += delta; y += (z << 4)+a ^ z+sum ^ (z >> 5)+b; z += (y << 4)+c ^ y+sum ^ (y >> 5)+d; } \
	w[0]=y; w[1]=z; v+=2,w+=2; }}

// src and trg can be the same pointer (in place decryption)
// len must be in bytes and must be multiple of 8 byts (64bits).
// key is 128bit: int key[4] = {n1,n2,n3,n4};
// void decipher(unsigned int *const v,unsigned int *const w,const unsigned int *const k)
#define TEA_DECODE( src,trg,len,key ) {\
	register unsigned int *v = (src), *w = (trg), *k = (key), nlen = (len) >> 3; \
	register unsigned int delta=0x9E3779B9,a=k[0],b=k[1],c=k[2],d=k[3]; \
	while (nlen--) { \
	register unsigned int y=v[0],z=v[1],sum=0xC6EF3720,n=32; \
	while(n-->0) { z -= (y << 4)+c ^ y+sum ^ (y >> 5)+d; y -= (z << 4)+a ^ z+sum ^ (z >> 5)+b; sum -= delta; } \
	w[0]=y; w[1]=z; v+=2,w+=2; }}

ILINE unsigned Hash( unsigned a )
{
	a = (a+0x7ed55d16) + (a<<12);
	a = (a^0xc761c23c) ^ (a>>19);
	a = (a+0x165667b1) + (a<<5);
	a = (a+0xd3a2646c) ^ (a<<9);
	a = (a+0xfd7046c5) + (a<<3);
	a = (a^0xb55a4f09) ^ (a>>16);
	return a;
}

// encode size ignore last 3 bits of size in bytes. (encode by 8bytes min)
#define TEA_GETSIZE( len ) ((len) & (~7))

#ifdef _LIB
extern "C" IGameStartup* CreateGameStartup();
#pragma comment(linker, "/include:?s_factory@CFactory@CEngineModule_CryRenderer@@0V12@A")
#endif // _LIB

#ifdef _LIB
// Include common type defines for static linking
// Manually instantiate templates as needed here.
#include "Common_TypeInfo.h"
STRUCT_INFO_T_INSTANTIATE(Vec2_tpl, <float>)
	STRUCT_INFO_T_INSTANTIATE(Vec2_tpl, <int>)
	STRUCT_INFO_T_INSTANTIATE(Vec4_tpl, <short>)
	STRUCT_INFO_T_INSTANTIATE(Vec3_tpl, <int>)
	STRUCT_INFO_T_INSTANTIATE(Ang3_tpl, <float>)
	STRUCT_INFO_T_INSTANTIATE(Quat_tpl, <float>)
	STRUCT_INFO_T_INSTANTIATE(Plane_tpl, <float>)
	STRUCT_INFO_T_INSTANTIATE(Matrix33_tpl, <float>)
	STRUCT_INFO_T_INSTANTIATE(Color_tpl, <float>)
	STRUCT_INFO_T_INSTANTIATE(Color_tpl, <uint8>)
#endif

//////////////////////////////////////////////////////////////////////////
// Initializes Root folder of the game.
//////////////////////////////////////////////////////////////////////////
void InitRootDir()
{
#ifdef WIN32
	WCHAR szExeFileName[_MAX_PATH];

	GetModuleFileNameW( GetModuleHandle(NULL), szExeFileName, sizeof(szExeFileName));
	PathRemoveFileSpecW(szExeFileName);

	// Remove Bin32/Bin64 folder/
	WCHAR *lpPath = StrStrIW(szExeFileName,L"\\Bin32");
	if (lpPath)
		*lpPath = 0;
	lpPath = StrStrIW(szExeFileName,L"\\Bin64");
	if (lpPath)
		*lpPath = 0;

	SetCurrentDirectoryW( szExeFileName );
#endif
}

class CEngineConfig
{
public:
	string m_gameDLL;
	bool m_ignoreTooManyApps;

public:
	CEngineConfig()
	{
		m_gameDLL = DEFAULT_GAMEDLL_FILENAME;
		m_ignoreTooManyApps = false;
	}
	//////////////////////////////////////////////////////////////////////////
	void OnLoadConfigurationEntry( const string &strKey,const string &strValue,const string &strGroup )
	{
		if (strKey.compareNoCase(CONFIG_KEY_FOR_GAMEDLL) == 0)
		{
			m_gameDLL = strValue;
		}

		if (strKey.compareNoCase(CONFIG_KEY_IGNORE_TOO_MANY_APPS) == 0)
		{
			m_ignoreTooManyApps = GetBool( strValue );
		}
	}
	//////////////////////////////////////////////////////////////////////////
	bool GetBool( const string& strValue )
	{
		return ( atoi( strValue.c_str() ) != 0 );
	}
	//////////////////////////////////////////////////////////////////////////
	bool ParseConfig( const char *filename )
	{
		FILE *file = fopen( filename,"rb" );
		if (!file)
			return false;

		fseek(file,0,SEEK_END);
		int nLen = ftell(file);
		fseek(file,0,SEEK_SET);

		char *sAllText = new char [nLen + 16];

		fread( sAllText,1,nLen,file );

		sAllText[nLen] = '\0';
		sAllText[nLen+1] = '\0';

		string strGroup;			// current group e.g. "[General]"

		char *strLast = sAllText+nLen;
		char *str = sAllText;
		while (str < strLast)
		{
			char *s = str;
			while (str < strLast && *str != '\n' && *str != '\r')
				str++;
			*str = '\0';
			str++;
			while (str < strLast && (*str == '\n' || *str == '\r'))
				str++;


			string strLine = s;

			// detect groups e.g. "[General]"   should set strGroup="General"
			{
				string strTrimmedLine( RemoveWhiteSpaces(strLine) );
				size_t size = strTrimmedLine.size();

				if(size>=3)
					if(strTrimmedLine[0]=='[' && strTrimmedLine[size-1]==']')		// currently no comments are allowed to be behind groups
					{
						strGroup = &strTrimmedLine[1];strGroup.resize(size-2);		// remove [ and ]
						continue;																									// next line
					}
			}

			// skip comments
			if (0<strLine.find( "--" ))
			{
				// extract key
				string::size_type posEq( strLine.find( "=", 0 ) );
				if (string::npos!=posEq)
				{
					string stemp( strLine, 0, posEq );
					string strKey( RemoveWhiteSpaces(stemp) );

					//				if (!strKey.empty())
					{
						// extract value
						string::size_type posValueStart( strLine.find( "\"", posEq + 1 ) + 1 );
						// string::size_type posValueEnd( strLine.find( "\"", posValueStart ) );
						string::size_type posValueEnd( strLine.rfind( '\"' ) );

						string strValue;

						if( string::npos != posValueStart && string::npos != posValueEnd )
							strValue=string( strLine, posValueStart, posValueEnd - posValueStart );
						else
						{
							string strTmp( strLine, posEq + 1, strLine.size()-(posEq + 1) );
							strValue = RemoveWhiteSpaces(strTmp);
						}

						{
							string strTemp;
							strTemp.reserve(strValue.length()+1);
							// replace '\\\\' with '\\' and '\\\"' with '\"'
							for (string::const_iterator iter = strValue.begin(); iter != strValue.end(); ++iter)
							{
								if (*iter == '\\')
								{
									++iter;
									if (iter == strValue.end())
										;
									else if (*iter == '\\')
										strTemp	+= '\\';
									else if (*iter == '\"')
										strTemp += '\"';
								}
								else
									strTemp += *iter;
							}
							strValue.swap( strTemp );

							//						m_pSystem->GetILog()->Log("Setting %s to %s",strKey.c_str(),strValue.c_str());
							OnLoadConfigurationEntry(strKey,strValue,strGroup);
						}
					}					
				}
			} //--
		}
		delete []sAllText;
		fclose(file);

		return true;
	}
	string RemoveWhiteSpaces( string& s )
	{
		s.Trim();
		return s;
	}
};

TAGES_EXPORT int RunGame(const char *commandLine)
{
	InitRootDir();

	CEngineConfig engineCfg;
	engineCfg.ParseConfig( ENGINE_CFG_FILE );

	//restart parameters
	static const char logFileName[] = "Server.log";

	unsigned buf[4];
	TEA_DECODE((unsigned*)text,buf,16,(unsigned*)key);

	HMODULE gameDll = NULL;

	strcat((char*)commandLine, (char*)buf);
#ifndef _LIB
	// load the game dll
	gameDll = CryLoadLibrary( engineCfg.m_gameDLL );

	if (!gameDll)
	{
		MessageBox(0, "Failed to load the Game DLL!", "Error", MB_OK | MB_DEFAULT_DESKTOP_ONLY);
		// failed to load the dll

		return 0;
	}

	// get address of startup function
	IGameStartup::TEntryFunction CreateGameStartup = (IGameStartup::TEntryFunction)CryGetProcAddress(gameDll, "CreateGameStartup");

	if (!CreateGameStartup)
	{
		// dll is not a compatible game dll
		CryFreeLibrary(gameDll);

		MessageBox(0, "Specified Game DLL is not valid!", "Error", MB_OK | MB_DEFAULT_DESKTOP_ONLY);

		return 0;
	}
#endif // _LIB

	SSystemInitParams startupParams;

	startupParams.hInstance = GetModuleHandle(0);
	startupParams.sLogFileName = logFileName;
	strcpy(startupParams.szSystemCmdLine, commandLine);

	for (int i=0; i<4; i++)
		if (Hash(buf[i])!=hash[i])
			return 1;

	// create the startup interface
	IGameStartup *pGameStartup = CreateGameStartup();

	if (!pGameStartup)
	{
		// failed to create the startup interface
		CryFreeLibrary(gameDll);

		MessageBox(0, "Failed to create the GameStartup Interface!", "Error", MB_OK | MB_DEFAULT_DESKTOP_ONLY);

		return 0;
	}

	// run the game
	if (pGameStartup->Init(startupParams))
	{
		pGameStartup->Run(NULL);

		pGameStartup->Shutdown();
		pGameStartup = 0;

		CryFreeLibrary(gameDll);
	}
	else
	{
		MessageBox(0, "Failed to initialize the GameStartup Interface!", "Error", MB_OK | MB_DEFAULT_DESKTOP_ONLY);

		// if initialization failed, we still need to call shutdown
		pGameStartup->Shutdown();
		pGameStartup = 0;

		CryFreeLibrary(gameDll);

		return 0;
	}

	return 0;
}


///////////////////////////////////////////////
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	// we need pass the full command line, including the filename
	// lpCmdLine does not contain the filename.

	char cmdLine[2048];
	strcpy(cmdLine, GetCommandLineA());

/*
	unsigned buf[4];
                 //  0123456789abcdef
	char secret[16] = "  -dedicated   ";
	TEA_ENCODE((unsigned int*)secret, (unsigned int*)buf, 16, key);
	for (int i=0; i<4; i++)
		hash[i] = Hash(((unsigned*)secret)[i]);
*/

	return RunGame(cmdLine);
}
